package com.bblocks.common.anti_reptile.data;

import com.bblocks.common.anti_reptile.config.AntiReptileProperties;
import com.bblocks.common.anti_reptile.config.bean.RuleComm;
import com.bblocks.common.anti_reptile.constant.AntiReptileConsts;
import com.bblocks.common.anti_reptile.constant.RuleTypeEnum;
import com.bblocks.common.anti_reptile.module.IpUserNumVo;
import com.bblocks.common.anti_reptile.module.RuleStatVO;
import com.bblocks.common.anti_reptile.module.RuleVO;
import com.bblocks.common.anti_reptile.rule.RuleData;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.client.RestHighLevelClient;
import org.redisson.api.LocalCachedMapOptions;
import org.redisson.api.RLocalCachedMap;
import org.redisson.api.RedissonClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Lazy;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * <p>Project: bb-anti-reptile - RuleDataFromEs</p>
 * <p>描述：提取ip/user访问规则-简单版本平均/最高qps
 * 1、系统未提取到数据，走默认规则
 * 2、默认规则未配置 则无规则
 * 3、ip 用户数-未提取到按1计算
 *
 * 4、保存数据（2大类）
 * </p>
 *
 * @Author Zhao [125043150@qq.com]
 * @Date 2024/3/25 10:36
 * @Version 1.0
 * @since 8
 */
@ConditionalOnClass({ RedissonClient.class, RestHighLevelClient.class})
@Slf4j
//@Component
public class RuleDataFromEs implements RuleData {
    private static final LocalCachedMapOptions localCacheOptions = LocalCachedMapOptions.defaults()
            // 用于淘汰清除本地缓存内的元素
            // 共有以下几种选择:
            // LFU - 统计元素的使用频率，淘汰用得最少（最不常用）的。
            // LRU - 按元素使用时间排序比较，淘汰最早（最久远）的。
            // SOFT - 元素用Java的WeakReference来保存，缓存元素通过GC过程清除。
            // WEAK - 元素用Java的SoftReference来保存, 缓存元素通过GC过程清除。
            // NONE - 永不淘汰清除缓存元素。
            .evictionPolicy(LocalCachedMapOptions.EvictionPolicy.LRU)
            // 如果缓存容量值为0表示不限制本地缓存容量大小
            .cacheSize(1000)
            // 以下选项适用于断线原因造成了未收到本地缓存更新消息的情况。
            // 断线重连的策略有以下几种：
            // CLEAR - 如果断线一段时间以后则在重新建立连接以后清空本地缓存
            // LOAD - 在服务端保存一份10分钟的作废日志
            //        如果10分钟内重新建立连接，则按照作废日志内的记录清空本地缓存的元素
            //        如果断线时间超过了这个时间，则将清空本地缓存中所有的内容
            // NONE - 默认值。断线重连时不做处理。
            .reconnectionStrategy(LocalCachedMapOptions.ReconnectionStrategy.CLEAR)
            // 以下选项适用于不同本地缓存之间相互保持同步的情况
            // 缓存同步策略有以下几种：
            // INVALIDATE - 默认值。当本地缓存映射的某条元素发生变动时，同时驱逐所有相同本地缓存映射内的该元素
            // UPDATE - 当本地缓存映射的某条元素发生变动时，同时更新所有相同本地缓存映射内的该元素
            // NONE - 不做任何同步处理
            .syncStrategy(LocalCachedMapOptions.SyncStrategy.INVALIDATE)
            // 每个Map本地缓存里元素的有效时间，默认毫秒为单位--10分钟
            .timeToLive(600, TimeUnit.SECONDS)
            // 每个Map本地缓存里元素的最长闲置时间，默认毫秒为单位--1分钟
            .maxIdle(60, TimeUnit.SECONDS);


    @Resource
    private AntiReptileProperties antiReptileProperties;
    @Lazy
    @Resource
    private RedissonClient redissonClient;
    /**
     * url频率数据Map
     */
    private  RLocalCachedMap<String, RuleStatVO> urlRates ;
    /**
     * ip用户量Map
     */
    private RLocalCachedMap<String, Integer> ipUsers;


    @PostConstruct
    public void init() {
        urlRates = redissonClient.getLocalCachedMap(AntiReptileConsts.BB_ANTI_PRE + URL_RATE,localCacheOptions);
        ipUsers = redissonClient.getLocalCachedMap(AntiReptileConsts.BB_ANTI_PRE + IP_USERS,localCacheOptions);
    }

    @Override
    public RuleVO theRule(@NonNull RuleTypeEnum ruleTypeEnum,@NonNull String url, String ip) {
        RuleComm rule = antiReptileProperties.ruleByType(ruleTypeEnum);
        if(rule == null){
            return null;
        }

        RuleStatVO statVO = urlRates.get(url);
        if(statVO == null){//使用默认值
            Integer defaultQps = rule.defaultQps();
            if(defaultQps == null){
                return null;
            }
            RuleVO result = RuleVO
                    .builder()
                    .type(ruleTypeEnum.getType())
                    .rateAvg(defaultQps)
                    .rateMax( defaultQps )
                    .build();
            return result;
        }else {
            Integer ratio = 1;
            if(ruleTypeEnum == RuleTypeEnum.IP_PLUS){
                ratio = ipUsers.get(ip);
                if(ratio == null || ratio <= 0)
                    ratio = 1;
            }
            RuleVO result = RuleVO
                    .builder()
                    .type(ruleTypeEnum.getType())
                    .rateAvg((ruleTypeEnum==RuleTypeEnum.USER ? statVO.getRateAvgUser() : statVO.getRateAvgIp()) * ratio)
                    .rateMax( (int)(rule.getMultiplier()/100.0 * (ruleTypeEnum==RuleTypeEnum.USER ? statVO.getRateMaxUser() : statVO.getRateMaxIp()) * ratio))
                    .build();
            return result;
        }
    }

    /**
     * 覆盖值，忽略空值
     * @param ruleStatVOParam
     */
    @Override
    public void saveRule(@NonNull RuleStatVO ruleStatVOParam) {
        RuleStatVO ruleStatVO = urlRates.get(ruleStatVOParam.getUrl());
        if(ruleStatVO == null){
            urlRates.put(ruleStatVOParam.getUrl(),ruleStatVOParam);
        }else{//把非空的值覆盖

            if(ruleStatVOParam.getRateAvgIp() != null){
                ruleStatVO.setRateAvgIp(ruleStatVOParam.getRateAvgIp());
            }
            if(ruleStatVOParam.getRateMaxIp() != null){
                ruleStatVO.setRateMaxIp(ruleStatVOParam.getRateMaxIp());
            }

            if(ruleStatVOParam.getRateAvgUser() != null){
                ruleStatVO.setRateAvgUser(ruleStatVOParam.getRateAvgUser());
            }
            if(ruleStatVOParam.getRateMaxUser() != null){
                ruleStatVO.setRateMaxUser(ruleStatVOParam.getRateMaxUser());
            }
            urlRates.put(ruleStatVOParam.getUrl(),ruleStatVO);
        }
    }

    /**
     *
     * @param userNoVo
     */
    @Override
    public void saveIpUser(@NonNull IpUserNumVo userNoVo) {
        ipUsers.put(userNoVo.getIp(),userNoVo.getUserNum());
    }


    @PreDestroy
    public void destroy() {
    }

}
